/*
    TwoPageView image viewer
    Copyright (C) 2006 Japan Manoretimedia Lab.

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*/
namespace TwoPageView
{
    // please refer TIFF 6.0 specification
    class CCITTFaxToTIFF : System.IDisposable
    {
        System.IO.BinaryWriter w;

        protected virtual void Dispose(bool disposing)
        {
            if (disposing) {
                w.Close();
            }
            // free native resources
        }

        public void Dispose()
        {
            Dispose(true);
            System.GC.SuppressFinalize(this);
        }

        public long Convert(byte[] data, System.Drawing.Size wh, string fileName)
        {
            System.IO.FileStream fs = new System.IO.FileStream(fileName, System.IO.FileMode.Create, System.IO.FileAccess.Write);
            System.IO.File.SetAttributes(fileName, System.IO.File.GetAttributes(fileName) | System.IO.FileAttributes.Temporary);
            w = new System.IO.BinaryWriter(fs);

            uint dataLength = (uint)data.Length;
            if (0 != (dataLength & 1)) {
                dataLength += 1; //< always even number
            }
            uint headerSize = 8;
            WriteTiffHeader(headerSize + dataLength);
            w.Write(data);
            if (0 != (data.Length & 1)) {
                byte v0 = 0;
                w.Write(v0);
            }

            ushort nIFD = 16;
            Write2BE(nIFD);

            WriteIFDDirectoryEntry(TiffTag.ImageWidth, TiffTagType.SHORT, 1, (uint)wh.Width);
            WriteIFDDirectoryEntry(TiffTag.ImageLength, TiffTagType.SHORT, 1, (uint)wh.Height);
            WriteIFDDirectoryEntry(TiffTag.BitsPerSample, TiffTagType.SHORT, 1, 1);
            WriteIFDDirectoryEntry(TiffTag.Compression, TiffTagType.SHORT, 1, 4); //< 3== CCITT T4, 4==CCITT T6
            WriteIFDDirectoryEntry(TiffTag.Photometric, TiffTagType.SHORT, 1, 0); //< 0==WhiteIsZero, 1==BlackIsZero

            WriteIFDDirectoryEntry(TiffTag.FillOrder, TiffTagType.SHORT, 1, 1);   //< 1== The high-order bit of the first compression code is stored in the high-order bit of byte 0, the next-highest bit of the first compression code is stored in the next-highest bit of byte 0, and so on.
            WriteIFDDirectoryEntry(TiffTag.StripOffsets, TiffTagType.LONG, 1, 8); //< offset to actual image data from the start of the file
            WriteIFDDirectoryEntry(TiffTag.Orientation, TiffTagType.SHORT, 1, 1); //< 1==The 0th row represents the visual top of the image, and the 0th column represents the visual left-hand side.
            WriteIFDDirectoryEntry(TiffTag.SamplesPerPixel, TiffTagType.SHORT, 1, 1);
            WriteIFDDirectoryEntry(TiffTag.RowsPerStrip, TiffTagType.LONG, 1, 0xffffffff);

            WriteIFDDirectoryEntry(TiffTag.StripByteCounts, TiffTagType.LONG, 1, (uint)data.Length);
            WriteIFDDirectoryEntry(TiffTag.XResolution, TiffTagType.RATIONAL, 1, (uint)(headerSize + dataLength + 2 + 12 * nIFD + 4));
            WriteIFDDirectoryEntry(TiffTag.YResolution, TiffTagType.RATIONAL, 1, (uint)(headerSize + dataLength + 2 + 12 * nIFD + 4 + 8));
            WriteIFDDirectoryEntry(TiffTag.PlanarConfiguration, TiffTagType.SHORT, 1, 1);
            WriteIFDDirectoryEntry(TiffTag.T6Options, TiffTagType.LONG, 1, 0); // bit0: unused, bit1: uncompressed/compressed

            WriteIFDDirectoryEntry(TiffTag.ResolutionUnit, TiffTagType.SHORT, 1, 2); //< Inch

            Write4BE(0);

            Write4BE(72); //< XResolution. offset == headerSize + dataLength + 2 + 12 * nIFD + 4
            Write4BE(1);
            Write4BE(72); //< YResolution.
            Write4BE(1);

            long fileLength = fs.Position;
            w.Close();
            fs.Close();
            return fileLength;
        }

        enum TiffTag
        {
            NewSubfileType = 254,

            ImageWidth = 256,
            ImageLength = 257,
            BitsPerSample = 258,
            Compression = 259,
            Photometric = 262,

            FillOrder = 266,
            StripOffsets = 273,
            Orientation = 274,
            SamplesPerPixel = 277,
            RowsPerStrip = 278,

            StripByteCounts = 279,
            XResolution = 282,
            YResolution = 283,
            PlanarConfiguration = 284,
            T4Options = 292,

            T6Options = 293,
            ResolutionUnit = 296,
            PageNumber = 297,
            Software = 305,
        }

        enum TiffTagType
        {
            ASCII = 2,
            SHORT = 3,
            LONG = 4,
            RATIONAL = 5,
        }

        private void WriteTiffHeader(uint directoryEntryOffset)
        {
            Write2BE(0x4d4d); //< 0x4d4d==big endian, 0x4949==little endian
            Write2BE(42);     //< TIFF magic number
            Write4BE(directoryEntryOffset);
        }

        private void WriteIFDDirectoryEntry(TiffTag tag, TiffTagType type, uint count, uint val)
        {
            Write2BE((ushort)tag);   // 0 - 1
            Write2BE((ushort)type);  // 2 - 3
            Write4BE(count); // 4 - 7
            switch (type) {
            case TiffTagType.SHORT:
                if (1 == count) {
                    Write2BE((ushort)val);
                    Write2BE(0);
                } else if (2 == count) {
                    Write4BE(val);
                } else {
                    System.Diagnostics.Debug.Assert(false);
                }
                break;
            case TiffTagType.LONG:
                if (1 == count) {
                    Write4BE(val);
                } else {
                    System.Diagnostics.Debug.Assert(false);
                }
                break;
            case TiffTagType.RATIONAL:
                if (1 == count) {
                    Write4BE(val);
                } else {
                    System.Diagnostics.Debug.Assert(false);
                }
                break;
            case TiffTagType.ASCII:
                if (count < 3) {
                    System.Diagnostics.Debug.Assert(false);
                }
                Write4BE(val);
                break;
            default:
                System.Diagnostics.Debug.Assert(false);
                break;
            }
        }

        private void Write2BE(ushort v)
        {
            byte x;
            x = (byte)((v >> 8) & 0xff);
            w.Write(x);
            x = (byte)(v & 0xff);
            w.Write(x);
        }

        private void Write4BE(uint v)
        {
            byte x;
            x = (byte)((v >> 24) & 0xff);
            w.Write(x);
            x = (byte)((v >> 16) & 0xff);
            w.Write(x);
            x = (byte)((v >> 8) & 0xff);
            w.Write(x);
            x = (byte)(v & 0xff);
            w.Write(x);
        }
    }
}
